/*
author: YiliangWu
实现cd相关
*/

#pragma once

#include <assert.h>
#include <time.h>
#include <set>
#include "../typedef.h"
#include "../cnt_typedef.h"
#include "su_timer.h"
#include "../../src/log_def.h" //日志宏， 不同项目需要修改

namespace su
{
	// 计时器，操作：开始，暂停，恢复，获取流逝时间
	class TimerCnt
	{
	public:
		enum State {
			TIMER_S_NULL,
			TIMER_S_RUN,
			TIMER_S_PAUSE,
		};
	public:
		TimerCnt()
			:m_start(0)
			, m_pause(0)
			, m_state(TIMER_S_NULL)
		{
		}
		State state();
		void start();
		void pause();
		void resume();
		//返回运行的时间但不结束
		time_t peerCurrentTimer() const;
		//返回运行的时间
		time_t end();
	private:
		time_t m_start;
		time_t m_pause;
		State m_state;
	};

	/*
	brief: 倒计时功能
	rule:
	) 无需定时器，查看时计算状态
	) 初始化正常状态,调用startCD进入cd状态
	) 时间流逝过CD周期后，会进入正常状态
	*/
	class CdDefault
	{
	public:
		//@period cd周期秒
		//param: start	cd开始时间。 0 表示初始化无CD
		explicit CdDefault(const time_t& period, time_t start = 0);
		bool IsCD() const;//true表示CD状态
		void StartCD();//开始新的CD状态
		void ClearCD();
		time_t GetCountDownTime() const;
		//改变CD周期，当前一CD冷却后，没调用isCD情况下调用，会先清CD。
		void ChangePeriod(const time_t& period);

		time_t GetStart() const { return m_start; }
		time_t GetPeriod() const { return m_period; }

	private:
		time_t m_start;
		time_t m_period;
	};

	/*
	brief:累积倒计时到指定数值，进入强制冷却状态
	规则:
	)初始化为正常状态，每次调用accumulate 都会累积倒计时间。
	)倒计时间大于最大累积时间时，变成强制冷却状态
	)强制冷却状态时，当倒计时间0才变成正常状态。
	*/
	template<typename Tick>
	class AccumulCDBase
	{
	public:
		AccumulCDBase(const Tick &forceCdDist)
			:m_endCdTick(Tick())
			, m_isForceCd(false)
			, m_forceCdDist(forceCdDist)
		{
		}
		virtual ~AccumulCDBase() {};

		bool IsForceCD()
		{
			if (CurrentTick() >= m_endCdTick)
			{
				m_isForceCd = false;
			}
			return m_isForceCd;
		}

		Tick AddCD(Tick accumTick)
		{
			const Tick current = CurrentTick();
			if (current >= m_endCdTick)
			{
				m_endCdTick = current + accumTick;
			}
			else
			{
				m_endCdTick += accumTick;
			}
			Tick accum = m_endCdTick - current;
			if (accum >= m_forceCdDist)
			{
				m_isForceCd = true;
			}
			return accum;
		}

		inline void ClearCD()
		{
			m_endCdTick = Tick();
			m_isForceCd = false;
		}

		inline Tick GetCD()
		{
			const Tick current = CurrentTick();
			if (current >= m_endCdTick)
			{
				return 0;
			}
			return m_endCdTick - current;
		}
		virtual Tick CurrentTick() const = 0;

	protected://don't access member data unless serialize control
		Tick m_endCdTick;			// cd ending tick
		bool m_isForceCd;
	private:
		Tick m_forceCdDist;			// force cd tick distance
	};

	/*
	brief:
	计算当前是第几个周期。 只合适周期长度为固定的类型。周，天，小时。
	静态函数 GetCurSpan支持月周期。 查月周期数参考 SysTime::GetMonthPeriodCnt
	时间相关概念：
		时间戳：或者叫 UTC时间戳。 即协调世界时, 国际标准时间公元1970年1月1日00:00：00以来的秒数。
		时区： 同一时间戳在不同地区的表示时间是不一样的。 比如同一时间戳， 北京时间，比utc时间 多8小时
			  例如 mktime(日期）获得时间戳， 不同本地时间返回结果不一样。
	规则:
	)period must be one of (week,day,hours,minue,half hours,half day)
	)limit<period 周期开始计算的时间（分界点）
	)从北京时间1970-01-04 00:00:00，周日 为周期值0开始计算,以前的日期一律用周期值0表示

	例如： 一个星期为周期， 24*3600*2（周二0时） 为分界点，某x周的星期一表示第y个周期。 那么x周的星期二表示y+1个周期。
	使用例子：
		CycleNum obj(3600*24, 3600);  //周期为日，分界点为1点
		uint32 last_num = 0;
		while(1)
		{
			uint32 cur_num = obj.GetCurrent();  返回1970-xxx到现在的周期数
			if (last_num != cur_num)
			{
				last_num = cur_num
				//to do 每日重置
			}
		}

	*/
	enum class CycleType : int64_t
	{
		MONTH = 0,
		ONE_MINUTE = 1*60,
		FIVE_MINUTE = 5*60,
		HALF_HOUR = 1800,
		HOUR = 3600,
		DAY = 3600 * 24,
		WEEK = 3600 * 24 * 7,
	};
	//系统本地时间 1970-01-04 00:00:00，周日 为周期值0开始计算,以前的日期一律用周期值0表示
	class CycleNum
	{
	public:
		//会根据本地时间utc+x不一样，生成不一样的utc时间戳值. 
		static const time_t START_SEC;
	private:
		time_t m_period = 0;	//周期时间长度。
		time_t m_start = 0;		//第一个周期开始时间。	

	public:
		CycleNum() {}
		//para period :周期
		//para limit：周期开始计算的时间. （等于分界点时间为周期x, 等于分界点时间-1秒周期为x-1）
		CycleNum(CycleType type, const time_t &limit = 0);
		CycleNum(time_t period, const time_t &limit = 0);
		//返回指定时间戳的周期数 
		//parameter: const time_t &CurrentTick, 时间戳
		time_t Get(const time_t &curTs) const;
		//返回当前时间的周期数
		time_t GetCurrent() const;

		//返回在当前，在当前周期的时间跨度。
		time_t GetCurSpan() const;

		//返回指定时间戳, 在当前周期的开始到目前时间跨度。
		static time_t GetCurSpan(time_t curTs, CycleType period);
		//计算1970xx开始到目前流逝周期数
		static time_t GetPeriodNum(time_t curTs, CycleType period);
		//计算ts1周期-ts2周期 周期差
		static time_t GetPeriodSpan(time_t ts1, time_t ts2, CycleType period);

	private:
		void Init(CycleType period, const time_t& limit = 0);

	};


	//brief:
	//类似CycleNum， 周期内，多个重置时间点。
	//比如：每周一，周二 FB 重置。 
	//  VecInt64 vec_limit;
	//	vec_limit.push_back(3600 * 24 * 1);//周一
	//	vec_limit.push_back(3600 * 24 * 2);//周二
	//	CycleMoreReset obj(3600 * 24 * 7, vec_limit);
	//0周 周1后 周期数为1
	//0周 周2后 周期数为2
	//1周 周1后 周期数为3
	//1周 周2后 周期数为4
	//。。。

	class CycleMoreReset
	{
	public:
		//para period 周期
		//para vec_limit 周期内再分割的时间点。 偏移值
		explicit CycleMoreReset(const time_t &period, const VecInt64 &vec_limit);
		//返回当前时间的周期数
		time_t GetCurCycleNum() const;

	private:
		VecInt64 m_vec_limit;
		time_t m_period;	//周期时间长度。
	};




	/*
	brief:用来计算 时间周期性增长的数值,比如游戏中每分钟加1体力。buf,debuf
	rule:
		1 Period 为周期数，每周期数值加1， m_num 加1
		2 m_num 周期增加最大只能到m_maxNum。
		3 函数addExtNum 可以额外增加m_num，没上限。
	*/

	struct PeriodAccumulate
	{
	public:
		//para: const time_t &period, 周期
		//para: uint32 max_num, 周期回复数值最大值
		PeriodAccumulate(const time_t &period, uint32 max_num);

		uint32 currentNum();

		void costNum(uint32 cost);

		void addExtNum(uint32 add);

		void setMaxNum(uint32 max_num);

	private:
		uint32 m_num;
		uint32 m_max_num;
		time_t m_last_cycle_cnt;  //最近访问周期数
		CycleNum m_period_cnt;
	};


	/*
	周期定时器, 使用例子：每天，12点 闹钟。 每整点5分钟闹钟
	规则： 
		每次到期回调注册函数。 
		新周期自动重新开始定时器（循环定时器）
		初始化刚好回调时刻， 不会导致回调。
	*/
	class CycleTimer
	{
		Timer m_timer;
		TimerCB m_f = nullptr;
		time_t m_timeOutSpan =0; //每周期内， 周期开始时间戳 到 到期时间 的跨度
		CycleType m_cycleType = CycleType::HOUR; 
		uint32_t m_timerTotalSec = 0;

	public:
		//@startSpan 每周期内， 周期开始时间戳 到 到期时间 的跨度. 
		// 比如 Start(CycleType::Hour, 10*60, ..) 表示 每次整点10分 回调
		//调用后，不一定 true == IsTiming
		void Start(CycleType cycleType, TimerCB f, time_t startSpan = 0);
		template< class MemFun, class T>
		inline void Start(CycleType cycleType, MemFun fun, T* ins, time_t startSpan = 0)
		{
			Start(cycleType, std::bind(fun, ins), startSpan);
		}
		void Stop();
		bool IsTiming();
		//su::timer定时器 开始到结束时间跨度
		uint32 GetTimerTotalSec() const; 

	private:
		void OnTimer();
		void StartExactly();
	};

	//查看是否在某周期内时间段。 查看时计算状态
	class CheckSpanInCycle
	{
		CycleType m_crycleType;
		time_t m_startSec = 0;//本周期开始 到 活动时间的开始时间的跨度。 秒 
		time_t m_endSec = 0;//本周期开始 到 活动时间的结束时间的跨度。 秒 . 0表示周期最后一秒
	public:
		//@startSpanSec , 本周期开始 到 活动时间的开始时间的跨度。 秒 
		//@endSpanSec , 本周期开始 到 活动时间的结束时间的跨度。 秒 . 0表示周期最后一秒
		//开启时间为 【startSec， endSec】
		bool Init(CycleType crycleType, time_t startSpanSec, time_t endSpanSec);
		//月周期初始化
		//@startDay 日， 范围【1，31】
		//@starDaySpan 某日0时开始，到开始时间的跨度
		//@endDay,endDaySpan  表达//本周期开始 到 活动时间的结束时间的跨度。 秒 . 0表示周期最后一秒
		bool InitMonthType(int startDay, time_t starDaySpan, int endDay, time_t endDaySpan);
		bool IsOpen() const;
	};

	//让某周期内时间段的 开始时刻 结束时刻 自动回调
	//如果在 [开始时刻, 结束时刻) 区间初始化，也会回调 OnStart()
	class SpanInCycle
	{
		CycleTimer m_startTimer;
		CycleTimer m_endTimer;
		bool m_isOpen = false;
	public:
		virtual void OnStart() = 0;
		virtual void OnEnd() = 0;
		//@startSec, endSec, 开始，结束 在当前周期的时间跨度 
		//@isMidWayOpen  true表示中途可以开启。 比如中途时间启动进程，会开启。否则不会。
		bool Init(CycleType cycleType, time_t startSec, time_t endSec, bool isMidWayOpen = false);
	private:
		void OnTimerStart();
		void OnTimerEnd();
	};
}